1 module test_allocator; 2 3 // tracks allocations and throws in the destructor if there is a memory leak 4 // it also throws when there is an attempt to deallocate memory that wasn't 5 // allocated 6 struct TestAllocator { 7 import std.experimental.allocator.common: platformAlignment; 8 import std.experimental.allocator.mallocator: Mallocator; 9 10 alias allocator = Mallocator.instance; 11 12 @safe @nogc nothrow: 13 14 private static struct ByteRange { 15 void* ptr; 16 size_t length; 17 inout(void)[] opSlice() @trusted @nogc nothrow pure inout { 18 return ptr[0 .. length]; 19 } 20 } 21 22 private ByteRange[] _allocations; 23 private int _numAllocations; 24 private char[1024] _textBuffer; 25 26 enum uint alignment = platformAlignment; 27 28 void[] allocate(size_t numBytes) scope { 29 import std.experimental.allocator: makeArray, expandArray; 30 31 ++_numAllocations; 32 33 auto ret = allocator.allocate(numBytes); 34 if(ret.length == 0) return ret; 35 36 auto newEntry = ByteRange(&ret[0], ret.length); 37 38 if(_allocations is null) 39 _allocations = allocator.makeArray(1, newEntry); 40 else 41 () @trusted { allocator.expandArray(_allocations, 1, newEntry); }(); 42 43 return ret; 44 } 45 46 bool deallocate(void[] bytes) scope pure { 47 import std.algorithm: remove, canFind; 48 static if (__VERSION__ < 2077) 49 { 50 import core.stdc.stdio: sprintf; 51 alias pureSprintf = sprintf; 52 } 53 54 bool pred(ByteRange other) { return other.ptr == bytes.ptr && other.length == bytes.length; } 55 56 static char[1024] buffer; 57 58 // @trusted because this is `scope` and we're taking the address of it 59 assert(() @trusted { return &this !is null; }(), "Attempting to deallocate when `this` is null"); 60 61 if(!_allocations.canFind!pred) { 62 auto index = pureSprintf( 63 () @trusted { return _textBuffer.ptr; }(), 64 "Cannot deallocate unknown byte range.\nPtr: %p, length: %ld, allocations:\n", 65 () @trusted { return bytes.ptr; }(), bytes.length); 66 index = printAllocations(_textBuffer, index); 67 _textBuffer[index] = 0; 68 debug 69 assert(false, _textBuffer[0 .. index].dup); 70 else 71 assert(false, "Cannot deallocate unknown byte range. Use debug mode to see more information"); 72 } 73 74 _allocations = _allocations.remove!pred; 75 76 return () @trusted { return allocator.deallocate(bytes); }(); 77 } 78 79 bool deallocateAll() scope pure { 80 foreach(ref allocation; _allocations) { 81 deallocate(allocation[]); 82 } 83 return true; 84 } 85 86 auto numAllocations() pure const scope { 87 return _numAllocations; 88 } 89 90 ~this() pure { 91 verify; 92 finalise; 93 } 94 95 private void finalise() scope pure { 96 import std.experimental.allocator: dispose; 97 deallocateAll; 98 () @trusted { allocator.dispose(_allocations); }(); 99 } 100 101 void verify() scope pure { 102 static if (__VERSION__ < 2077) 103 { 104 import core.stdc.stdio: sprintf; 105 alias pureSprintf = sprintf; 106 } 107 108 if(_allocations.length) { 109 auto index = pureSprintf( 110 () @trusted { return _textBuffer.ptr; }(), 111 "Memory leak in TestAllocator. Allocations:\n"); 112 index = printAllocations(_textBuffer, index); 113 _textBuffer[index] = 0; 114 115 finalise; // avoid asan leaks 116 117 debug 118 assert(false, _textBuffer[0 .. index].dup); 119 else 120 assert(false, "Memory leak in TestAllocator. Use debug mode to see more information"); 121 } 122 } 123 124 int printAllocations(int N)(ref char[N] buffer, int index = 0) pure const scope { 125 static if (__VERSION__ < 2077) 126 { 127 import core.stdc.stdio: sprintf; 128 alias pureSprintf = sprintf; 129 } 130 131 index += pureSprintf(&buffer[index], "["); 132 133 if(_allocations !is null) { 134 foreach(ref allocation; _allocations) { 135 index += pureSprintf(&buffer[index], "ByteRange(%p, %ld), ", 136 allocation.ptr, allocation.length); 137 } 138 } 139 140 index += pureSprintf(&buffer[index], "]"); 141 return index; 142 } 143 } 144 145 static if (__VERSION__ >= 2077) 146 { 147 /* Private bits that allow sprintf to become pure */ 148 private int pureSprintf(A...)(scope char* s, scope const(char*) format, A va) 149 @trusted pure nothrow 150 { 151 const errnosave = fakePureErrno(); 152 const ret = fakePureSprintf(s, format, va); 153 fakePureErrno() = errnosave; 154 155 return ret; 156 } 157 158 extern (C) private @system @nogc nothrow 159 { 160 version(DigitalMars) { 161 ref int fakePureErrnoImpl() 162 { 163 import core.stdc.errno; 164 return errno(); 165 } 166 } else 167 ref int fakePureErrnoImpl(); 168 } 169 170 extern (C) private pure @system @nogc nothrow 171 { 172 pragma(mangle, "fakePureErrnoImpl") ref int fakePureErrno(); 173 pragma(mangle, "sprintf") int fakePureSprintf(scope char* s, scope const(char*) format, ...); 174 } 175 } 176 177 @safe @nogc nothrow unittest { 178 import std.experimental.allocator : allocatorObject; 179 import std.experimental.allocator.building_blocks.stats_collector; 180 import std.experimental.allocator.mallocator: Mallocator; 181 import std.conv : to; 182 183 alias SCAlloc = StatsCollector!(TestAllocator, Options.bytesUsed); 184 185 SCAlloc allocator; 186 auto buf = allocator.allocate(10); 187 allocator.deallocate(buf); 188 assert(allocator.bytesUsed == 0); 189 } 190 191 192 @safe @nogc nothrow unittest { 193 auto obj = TestAllocator(); 194 scope ptr = &obj; 195 } 196 197 198 @safe @nogc unittest { 199 import std.experimental.allocator: makeArray, expandArray, dispose; 200 auto allocator = TestAllocator(); 201 auto array = allocator.makeArray!int(3); 202 // expandArray is @system because Mallocator.reallocate is @system, 203 // and that in turn is because reallocate may make pointers dangle. 204 const expanded = () @trusted { return allocator.expandArray(array, 2); }(); 205 allocator.dispose(array); 206 }